#include "drv_flash_w25qxx.h"

#define DRV_DEBUG
#define LOG_TAG "flash"
#include <drv_log.h>

#define FLASH_CS_PIN    BSP_SPI1_CS_PIN

#define W25QXX_PAGE_SIZE                    (256)
#define W25QXX_SECTOR_SIZE                  (4 * 1024)
#define W25QXX_BLOCK_32K_SIZE               (32 * 1024)
#define W25QXX_BLOCK_64K_SIZE               (64 * 1024)
#define W25QXX_CHIP_SIZE                    (16 * 1024 * 1024)
/* w25qxx instruction set */
#define W25QXX_CMD_WRITE_ENABLE             0x06
#define W25QXX_CMD_WRITE_DISABLE            0x04
#define W25QXX_CMD_VOLATILE_SR_WRITE_ENABLE 0x50
#define W25QXX_CMD_RESET_ENABLE             0x66
#define W25QXX_CMD_RESET                    0x99
#define W25QXX_CMD_READ_STATUS_REGISTER1    0x05
#define W25QXX_CMD_READ_STATUS_REGISTER2    0x35
#define W25QXX_CMD_READ_STATUS_REGISTER3    0x15
#define W25QXX_CMD_READ_DATA                0x03 
#define W25QXX_CMD_PAGE_PROGRAM             0x02
#define W25QXX_CMD_SECTOR_ERASE             0x20
#define W25QXX_CMD_32K_BLOCK_ERASE          0x52
#define W25QXX_CMD_64K_BLOCK_ERASE          0xD8
#define W25QXX_CMD_CHIP_ERASE               0xC7

static void w25qxx_write_instruction(struct rt_spi_device *spi_dev, rt_uint8_t cmd)
{
    rt_spi_send(spi_dev, &cmd, 1);
}
static rt_uint8_t w25qxx_read_reg(struct rt_spi_device *spi_dev, rt_uint8_t cmd)
{
    rt_uint8_t value;
    rt_spi_send_then_recv(spi_dev, &cmd, 1, &value, 1);
    return value;
}
static void w25qxx_software_reset(struct rt_spi_device *spi_dev)
{
    w25qxx_write_instruction(spi_dev, W25QXX_CMD_RESET_ENABLE);
    w25qxx_write_instruction(spi_dev, W25QXX_CMD_RESET);
    rt_thread_mdelay(1);
}
static rt_bool_t is_w25qxx_busy(struct rt_spi_device *spi_dev)
{
    rt_uint8_t value;
    value = w25qxx_read_reg(spi_dev, W25QXX_CMD_READ_STATUS_REGISTER1);
    return ((value & 0x01) == 0x01);
}
static void w25qxx_read(struct rt_spi_device *spi_dev, rt_uint32_t addr, rt_uint8_t *data_buf, rt_uint32_t size)
{
    rt_uint8_t send_buf[4];
    send_buf[0] = W25QXX_CMD_READ_DATA;
    send_buf[1] = (addr >> 16) & 0xFF;
    send_buf[2] = (addr >> 8) & 0xFF;
    send_buf[3] = addr & 0xFF;
    rt_spi_send_then_recv(spi_dev, send_buf, sizeof(send_buf), data_buf, size);
}
static void w25qxx_sector_erase(struct rt_spi_device *spi_dev, rt_uint32_t addr)
{
    rt_uint8_t send_buf[4];
    
    while(is_w25qxx_busy(spi_dev) == RT_TRUE)
        rt_thread_mdelay(1);

    w25qxx_write_instruction(spi_dev, W25QXX_CMD_WRITE_ENABLE);
    send_buf[0] = W25QXX_CMD_SECTOR_ERASE;
    send_buf[1] = (addr >> 16) & 0xFF;
    send_buf[2] = (addr >> 8) & 0xFF;
    send_buf[3] = addr & 0xFF;
    rt_spi_send(spi_dev, send_buf, sizeof(send_buf));
}
static void w25qxx_32k_block_erase(struct rt_spi_device *spi_dev, rt_uint32_t addr)
{
    rt_uint8_t send_buf[4];
    
    while(is_w25qxx_busy(spi_dev) == RT_TRUE)
        rt_thread_mdelay(1);

    w25qxx_write_instruction(spi_dev, W25QXX_CMD_WRITE_ENABLE);
    send_buf[0] = W25QXX_CMD_32K_BLOCK_ERASE;
    send_buf[1] = (addr >> 16) & 0xFF;
    send_buf[2] = (addr >> 8) & 0xFF;
    send_buf[3] = addr & 0xFF;
    rt_spi_send(spi_dev, send_buf, sizeof(send_buf));
}
static void w25qxx_64k_block_erase(struct rt_spi_device *spi_dev, rt_uint32_t addr)
{
    rt_uint8_t send_buf[4];
    
    while(is_w25qxx_busy(spi_dev) == RT_TRUE)
        rt_thread_mdelay(1);

    w25qxx_write_instruction(spi_dev, W25QXX_CMD_WRITE_ENABLE);
    send_buf[0] = W25QXX_CMD_64K_BLOCK_ERASE;
    send_buf[1] = (addr >> 16) & 0xFF;
    send_buf[2] = (addr >> 8) & 0xFF;
    send_buf[3] = addr & 0xFF;
    rt_spi_send(spi_dev, send_buf, sizeof(send_buf));
}
static void w25qxx_chip_erase(struct rt_spi_device *spi_dev)
{
    while(is_w25qxx_busy(spi_dev) == RT_TRUE)
        rt_thread_mdelay(1);

    w25qxx_write_instruction(spi_dev, W25QXX_CMD_WRITE_ENABLE);
    w25qxx_write_instruction(spi_dev, W25QXX_CMD_CHIP_ERASE);
}
static void w25qxx_page_program(struct rt_spi_device *spi_dev, rt_uint32_t addr, rt_uint8_t *data_buf, rt_uint32_t size)
{
    rt_uint8_t send_buf[4];
    if(size > W25QXX_PAGE_SIZE)
    {
        rt_kprintf("page program size is too large\n");
        return;
    }
    while(is_w25qxx_busy(spi_dev) == RT_TRUE)
        rt_thread_mdelay(1);

    w25qxx_write_instruction(spi_dev, W25QXX_CMD_WRITE_ENABLE);
    send_buf[0] = W25QXX_CMD_PAGE_PROGRAM;
    send_buf[1] = (addr >> 16) & 0xFF;
    send_buf[2] = (addr >> 8) & 0xFF;
    send_buf[3] = addr & 0xFF;
    rt_spi_send_then_send(spi_dev, send_buf, sizeof(send_buf), data_buf, size);
}
static void w25qxx_program(struct rt_spi_device *spi_dev, rt_uint32_t addr, rt_uint8_t *data_buf, rt_uint32_t size)
{
    rt_uint32_t current_addr;
    rt_uint8_t *current_data_ptr;
    rt_uint16_t current_page_remain;
    rt_uint32_t data_remain;

    current_addr = addr;
    current_data_ptr = data_buf;
    data_remain = size;
    current_page_remain = W25QXX_PAGE_SIZE - (addr & (W25QXX_PAGE_SIZE - 1));
    if(current_page_remain >= data_remain)
    {
        w25qxx_page_program(spi_dev, current_addr, current_data_ptr, data_remain);
    }
    else
    {
        w25qxx_page_program(spi_dev, current_addr, current_data_ptr, current_page_remain);
        current_addr += current_page_remain;
        current_data_ptr += current_page_remain;
        data_remain -= current_page_remain;

        while(data_remain > 0)
        {
            if(data_remain > W25QXX_PAGE_SIZE)
            {
                w25qxx_page_program(spi_dev, current_addr, current_data_ptr, W25QXX_PAGE_SIZE);
                current_addr += W25QXX_PAGE_SIZE;
                current_data_ptr += W25QXX_PAGE_SIZE;
                data_remain -= W25QXX_PAGE_SIZE;
            }
            else
            {
                w25qxx_page_program(spi_dev, current_addr, current_data_ptr, data_remain);
                data_remain = 0;
            }
        }
    }
}

static void flash_erase_sector(struct rt_w25qxx_device *dev, rt_uint32_t addr)
{
    w25qxx_sector_erase(dev->spi_dev, addr);
}
static void flash_erase_32k_block(struct rt_w25qxx_device *dev, rt_uint32_t addr)
{
    w25qxx_32k_block_erase(dev->spi_dev, addr);
}
static void flash_erase_64k_block(struct rt_w25qxx_device *dev, rt_uint32_t addr)
{
    w25qxx_64k_block_erase(dev->spi_dev, addr);
}
static void flash_erase_chip(struct rt_w25qxx_device *dev)
{
    w25qxx_chip_erase(dev->spi_dev);
}
static void flash_program(struct rt_w25qxx_device *dev, rt_uint32_t addr, rt_uint8_t *data_buf, rt_uint32_t size)
{
    w25qxx_program(dev->spi_dev, addr, data_buf, size);
}
static void flash_read(struct rt_w25qxx_device *dev, rt_uint32_t addr, rt_uint8_t *data_buf, rt_uint32_t size)
{
    w25qxx_read(dev->spi_dev, addr, data_buf, size);
}
static int rt_hw_spi_flash_init(void)
{
    struct rt_w25qxx_device *flash_dev = RT_NULL;
    struct rt_spi_configuration config; 
    flash_dev = (struct rt_w25qxx_device *)rt_malloc(sizeof(struct rt_w25qxx_device));
    if(flash_dev == RT_NULL)
    {
        LOG_E("no memory for w25qxx device");
        return -RT_ENOMEM;
    }
    flash_dev->spi_dev = (struct rt_spi_device *)rt_malloc(sizeof(struct rt_spi_device));
    if(flash_dev->spi_dev == RT_NULL)
    {
        LOG_E("no memory for spi device");
        return -RT_ENOMEM;
    }
    if(rt_spi_bus_attach_device_cspin(flash_dev->spi_dev, "spi10", "spi1", FLASH_CS_PIN, RT_NULL) != RT_EOK)
    {
        LOG_E("failed to attach device to spi bus");
        return -RT_ERROR;
    }
    config.data_width = 8;
    config.mode = RT_SPI_MASTER | RT_SPI_MODE_0 | RT_SPI_MSB;
    config.max_hz = 20 * 1000 * 1000;
    rt_spi_configure(flash_dev->spi_dev, &config);
    flash_dev->spi_dev->parent.type = RT_Device_Class_Block;
    flash_dev->spi_dev->parent.init = RT_NULL;
    flash_dev->spi_dev->parent.open = RT_NULL;
    flash_dev->spi_dev->parent.close = RT_NULL;
    flash_dev->spi_dev->parent.read = RT_NULL;
    flash_dev->spi_dev->parent.write = RT_NULL;
    flash_dev->spi_dev->parent.control = RT_NULL;
    flash_dev->erase_sector = flash_erase_sector;
    flash_dev->erase_32k_block = flash_erase_32k_block;
    flash_dev->erase_64k_block = flash_erase_64k_block;
    flash_dev->erase_chip = flash_erase_chip;
    flash_dev->program = flash_program;
    flash_dev->read = flash_read;
    rt_device_register(&flash_dev->spi_dev->parent, "flash", RT_DEVICE_FLAG_RDWR);
    return RT_EOK;
}

INIT_DEVICE_EXPORT(rt_hw_spi_flash_init);